package cn.qfys521.mc_websocket.client

import cn.qfys521.mc_websocket.client.models.Player
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import java.net.URI

class ManagementWsClientEncodingTest {

    @Test
    fun `buildRequest wraps params as single-element array`() {
        val client = ManagementWsClient(URI("ws://localhost:25585"), config = ManagementWsClient.ReconnectConfig(autoReconnect = false))
        val payload = listOf(Player(name = "jeb_", id = "uuid1"), Player(name = "steve", id = "uuid2"))
        val obj: JsonObject = client.buildRequest(
            method = "minecraft:allowlist/set",
            id = 1,
            params = payload,
            paramsSerializer = ListSerializer(Player.serializer())
        )
        val params = obj["params"] as JsonArray
        assertEquals(1, params.size)
        val inner = params[0].jsonArray
        assertEquals(2, inner.size)
        val first = inner[0].jsonObject
        assertEquals("uuid1", first["id"]?.jsonPrimitive?.content)
        assertEquals("jeb_", first["name"]?.jsonPrimitive?.content)
    }

    @Test
    fun `buildRequest omits params when null`() {
        val client = ManagementWsClient(URI("ws://localhost:25585"), config = ManagementWsClient.ReconnectConfig(autoReconnect = false))
        val obj: JsonObject = client.buildRequest(
            method = "minecraft:players",
            id = 2,
            params = null,
            paramsSerializer = ListSerializer(String.serializer())
        )
        assertFalse(obj.containsKey("params"))
        assertEquals("minecraft:players", obj["method"]?.jsonPrimitive?.content)
    }

    @Test
    fun `decodeResultFromResponse decodes list of players`() {
        val client = ManagementWsClient(URI("ws://localhost:25585"), config = ManagementWsClient.ReconnectConfig(autoReconnect = false))
        val resp = """{"jsonrpc":"2.0","id":1,"result":[{"id":"u","name":"n"}]}"""
        val players: List<Player> = client.decodeResultFromResponse(ListSerializer(Player.serializer()), resp)
        assertEquals(1, players.size)
        assertEquals("u", players[0].id)
        assertEquals("n", players[0].name)
    }

    @Test
    fun `decodeResultFromResponse maps RPC error`() {
        val client = ManagementWsClient(URI("ws://localhost:25585"), config = ManagementWsClient.ReconnectConfig(autoReconnect = false))
        val resp = """{"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"Invalid params","data":{"hint":"bad"}}}"""
        val ex = assertThrows(ManagementException.Rpc::class.java) {
            client.decodeResultFromResponse(ListSerializer(Player.serializer()), resp)
        }
        assertEquals(-32602, ex.code)
        assertTrue(ex.message!!.contains("Invalid params"))
    }

    @Test
    fun `ensureOpen timeout throws`() {
        val client = ManagementWsClient(URI("ws://localhost:25585"), config = ManagementWsClient.ReconnectConfig(autoReconnect = false, connectWaitTimeoutMs = 50))
        val ex = assertThrows(ManagementException.Timeout::class.java) {
            // Run suspend in blocking context via kotlin test infra is not provided; emulate minimal by calling ensureOpen through runBlocking-like gate
            // Here we directly call ensureOpen in a separate thread to avoid blocking test runner
            var thrown: Throwable? = null
            val t = Thread {
                try { kotlinx.coroutines.runBlocking { client.ensureOpen(5000) } } catch (e: Throwable) { thrown = e }
            }
            t.start(); t.join()
            if (thrown != null) throw thrown else error("expected throw")
        }
        assertTrue(ex.message!!.contains("Timed out"))
    }
}

